#!CRUSH_PERL_PATH -w
#-*-perl-*-

#  Copyright 2008 Google Inc.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

use strict;
use Getopt::Long;
use DBI;
use FileHandle;

my ($dsn, $uid, $pwd);
$dsn = $ENV{'DB_DSN'};
$uid = $ENV{'DB_UID'};
$pwd = $ENV{'DB_PWD'};

my $CRUSH_DEBUG = $ENV{'CRUSH_DEV_DEBUG'};

# --file or --sql contain the query.
# --input will be a stream of values for insert or update queries
# --delimiter is provided for spliting input lines and/or joining output.

my ($help, $sqlfile, $sql, $infile, $outfile, $d, $show_version, $include_header);
$d = $ENV{"DELIMITER"} || chr(0xfe);
Getopt::Long::Configure( "no_ignore_case" );
GetOptions('help' => \$help,
           'file=s' => \$sqlfile,
           'sql=s' => \$sql,
           'input=s' => \$infile,
           'output=s' => \$outfile,
           'delimiter=s' => \$d,
           'dsn=s' => \$dsn,
           'uid=s' => \$uid,
           'pwd=s' => \$pwd,
           'Version' => \$show_version,
           'Header'  => \$include_header,
          );

if ($help) {
  usage();
  exit(1);
}
if ($show_version) {
  crush_version();
  exit(0);
}

$d = expand_chars($d);

if (! (defined($dsn) && defined($uid) && defined($pwd) )) {
  print STDERR "$0: need db connect info in environment vars DB_[DSN|UID|PWD]\n";
  exit(1);
}

my $query;
if (defined($sql)) {
  $query = $sql;
} elsif (defined($sqlfile)) {
  open(SFILE, $sqlfile) or die "$0: $sqlfile: $!\n";
  $query = join '', <SFILE>;
  close(SFILE);
} else {
  usage();
  print STDERR "$0: -s or -f must be specified.\n";
  exit(1);
}

my $fhout;
if (defined($outfile) and $outfile ne '-') {
  $fhout = new FileHandle($outfile, "w");
  die "$0: $outfile: $!\n" unless $fhout;
} else {
  $fhout = new FileHandle;
  $fhout->fdopen("STDOUT", "w");
  die "$0: stdout: $!\n" unless $fhout;
}

my $fhin;
if (defined($infile) and $infile ne '-') {
  $fhin = new FileHandle($infile, "r");
  die "$0: $infile: $!\n" unless $fhin;
} else {
  $fhin = new FileHandle;
  $fhin->fdopen("STDIN", "r");
  die "$0: stdin: $!\n" unless $fhin;
}

my $dbh = DBI->connect($dsn, $uid, $pwd, {RaiseError => 1, AutoCommit => 1} );
my $dbq = $dbh->prepare($query);
my $header_printed = 0;

# NOTE: Some DB Drivers require an execute() before the Statement handle has
# access to NUM_OF_FIELDS.
if ($dbq->{NUM_OF_PARAMS}) {
  # This query has placeholders.
  my $input_line;
  while ($input_line = <$fhin>) {
    $input_line =~ s/[\r\n]*//;
    print STDERR "$0: Executing with placeholder values: $input_line\n" if $CRUSH_DEBUG;
    chomp(my @vals = split(/\Q$d\E/o, $input_line));
    $dbq->execute(@vals);
    if ($dbq->{NUM_OF_FIELDS}) {
      # DB-read query.
      if (! $header_printed && $include_header) {
        print $fhout join($d, @{ $dbq->{NAME} }), qq(\n);
        $header_printed = 1;
      }
      fetch_and_print_results($dbq, $fhout);
    }
  }
} else {
  print STDERR "$0: Executing with no placeholder values\n" if $CRUSH_DEBUG;
  $dbq->execute();
  if ($dbq->{NUM_OF_FIELDS}) {
    # DB-read query.
    if (! $header_printed && $include_header) {
      print $fhout join($d, @{ $dbq->{NAME} }), qq(\n);
      $header_printed = 1;
    }
    fetch_and_print_results($dbq, $fhout);
  }
}
 
$dbh->disconnect();
$fhin->close;
$fhout->close;

exit(0);


sub fetch_and_print_results {
  my ($qh, $fh) = @_;
  while (my $results = $qh->fetchrow_arrayref()) {
    for (my $ri = 0; $ri < scalar(@{ $results }); $ri++) {
      if (! defined($results->[$ri])) {
        $results->[$ri] = '';
      }
    }
    print $fh join($d, @{ $results }) , qq(\n);
  }
}


sub usage {
  print STDERR << "ENDUSAGE";

runs a query.

usage: $0 <-f <sql-file> | -s <query>> [options]

options:

  -h, --help           print this message and exit
  -H, --Header         output the sql field names
  -f, --file <file>    take sql from specified file
  -s, --sql <query>    take sql on the commandline
  -i, --input <file>   input of placeholder values (default: stdin)
  -o, --output <file>  write to specified file (default: stdout)
  --delimiter <delim>  use specified field separator for input and output
  --dsn <dsn>          use DBI-style data-source name
  --uid <username>     database username
  --pwd <password>     database password

If --dsn, --uid, or --pwd aren't specified, environment variables DB_DSN,
DB_UID, and DB_PWD will be used for the unprovided values.

If the query contains value placeholders ('?'), values for those placeholders
will be taken from the file named by --input, or from STDIN.

ENDUSAGE
}

m4_include(utils.pl)
